package com.hex.es.autoconfig;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.vanroy.springboot.autoconfigure.data.jest.ElasticsearchJestDataAutoConfiguration;
import com.github.vanroy.springdata.jest.JestElasticsearchTemplate;
import com.github.vanroy.springdata.jest.mapper.DefaultJestResultsMapper;
import com.github.vanroy.springdata.jest.mapper.JestResultsMapper;
import com.hex.es.component.DefaultExecutionLinkController;
import com.hex.es.service.EsBulkOperationSerivce;
import com.hex.es.service.ExecutionLinkAfterInterface;
import com.hex.es.service.ExecutionLinkControllerInterface;
import com.hex.es.service.impl.DefaultEsBulkOperationImpl;
import com.hex.es.service.impl.DefaultExecutionLinkAfterImpl;
import io.searchbox.client.JestClient;
import lombok.NonNull;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.DefaultEntityMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.Optional;

/**
 * @description:
 * @author: Hex
 * @date: 2019/5/30 17:51
 */
@Configuration
@ComponentScan("com.hex.es")
@AutoConfigureBefore(ElasticsearchJestDataAutoConfiguration.class)
public class EsAutoConfig {

    private final ObjectMapper objectMapper;

    @Bean(name = "jestElasticsearchTemplate")
    @ConditionalOnMissingBean
    public ElasticsearchOperations elasticsearchTemplate(JestClient client) {
        ElasticsearchConverter elasticsearchConverter = new MappingElasticsearchConverter(
                new SimpleElasticsearchMappingContext());
        JestResultsMapper resultsMapper = new DefaultJestResultsMapper(elasticsearchConverter.getMappingContext(),
                new CMSDefaultEntityMapper(this.objectMapper, elasticsearchConverter.getMappingContext()));
        return new JestElasticsearchTemplate(client, elasticsearchConverter, resultsMapper);
    }

    @Bean
    @ConditionalOnMissingBean
    public ExecutionLinkControllerInterface buildExecutionLink(){
        return new DefaultExecutionLinkController();
    }

    @Bean
    @ConditionalOnMissingBean
    public ExecutionLinkAfterInterface buildExecutionLinkAfter(){
        return new DefaultExecutionLinkAfterImpl();
    }

    @Bean
    @ConditionalOnMissingBean
    public EsBulkOperationSerivce buildEsBulkOperationSerivce(){
        return new DefaultEsBulkOperationImpl();
    }

    /**
     * Constructor 构造方法
     */
    public EsAutoConfig(@NonNull ObjectProvider<ObjectMapper> objectMapperProvider,
                        @NonNull ObjectProvider<JestObjectMapper> jestObjectMapperProvider) {
        ObjectMapper objectMapper = Optional.ofNullable(objectMapperProvider.getIfAvailable())
                .map(mapper -> mapper.copy())
                .orElseGet(() -> null);

        if (objectMapper != null) {
            Optional.ofNullable(jestObjectMapperProvider.getIfAvailable())
                    .ifPresent(customizer -> customizer.customize(objectMapper));
        }
        this.objectMapper = objectMapper;
    }

    public interface JestObjectMapper {

        void customize(ObjectMapper objectMapper);
    }

    /**
     * 自定义默认实体对象映射
     */
    public static class CMSDefaultEntityMapper extends DefaultEntityMapper {

        public CMSDefaultEntityMapper(ObjectMapper objectMapper, MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
            super(context);
            if (objectMapper != null) {
                Field field = ReflectionUtils.findField(DefaultEntityMapper.class, "objectMapper");
                Assert.notNull(field, "Field 'objectMapper' is not null");
                ReflectionUtils.makeAccessible(field);
                ReflectionUtils.setField(field, this, objectMapper);
            }
        }
    }
}
